Skip to content

Conversation

maxstevens-nl
Copy link
Collaborator

@maxstevens-nl maxstevens-nl commented Sep 9, 2025

This PR adds support for Synced Queries in a backwards compatible way. This is done by introducing a new createZero composable, which manages the lifecycle of a zero instance, and returns useZero and useQuery composables.

To maintain backwards compatibility, useQuery is still exported but marked deprecated.

Copy link
Collaborator

@arv arv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@maxstevens-nl
Copy link
Collaborator Author

@danielroe good to merge?

@danielroe
Copy link
Owner

apologies for the delay. I want to review with an eye to how we would implement in nuxt (where provide/inject is often not the best pattern).

ideally something that would support lazy creation of the stateful singleton (ie on first access)

@Gerbuuun
Copy link
Contributor

I'm doing this. A global variable and checking if it exist on each access. I watch the user session but most of the time changing the value comes with navigation/reload so not sure how necessary it is.

import { Zero } from "@rocicorp/zero";
import { schema, clientMutators } from "~~/zero";
import type { AuthData } from "~~/zero";
import { decodeJwt } from "jose";

let client: Zero<typeof schema, ReturnType<typeof clientMutators>>;

export function useZero() {
  const { session } = useUserSession();
  const config = useRuntimeConfig().public.zero;
  const decodedJWT = computed<AuthData | undefined>(() => session.value?.token ? decodeJwt<AuthData>(session.value.token) : undefined);
  const userID = computed(() => decodedJWT.value?.sub ?? 'anon');

  watch([userID], () => {
    if (client && client.userID === userID.value)
      return;
      
    if (client && !client.closed) {
      client.close();
    }

    client = new Zero({
      userID: userID.value,
      auth: () => session.value?.token,
      mutators: clientMutators(decodedJWT.value),
      server: import.meta.client ? config.server : undefined,
      schema,
    });
  }, { immediate: true });

  return client;
}

@maxstevens-nl
Copy link
Collaborator Author

@danielroe would doing something along the lines of what Pinia does:

export let activeZero: Zero<any, any> | undefined

export function setActiveZero(zero: Zero<any, any>) {
  activeZero = zero
}

export function useZero() {
  return ((hasInjectionContext() && inject(zeroSymbol)) || shallowRef(activeZero))
}

in combination with the plugin proposed in this PR and a to-be-created Nuxt module be a step in the right direction?

While not lazy-creation, it would make sure that Zero is available during SSR. You also keep the benefit of useZero returning a shallowRef (in an injection context).

Although I need to add that I don't have a lot of experience with Nuxt, so if you know of any libraries that have a more 'nuxt-native' implementation that would be very welcome.

@maxstevens-nl
Copy link
Collaborator Author

@danielroe could you put in a review?

@maxstevens-nl maxstevens-nl reopened this Oct 14, 2025
Copy link

socket-security bot commented Oct 14, 2025

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@Gerbuuun
Copy link
Contributor

The problem I have when looking at this solution is that I cannot run multiple zero sessions side by side (something I'm doing atm). Can you think of a way that provides the instance a different way? Right now that won't work

What I tried for example:

export function createZero() {
  let zero;

  function useZero() {
    // init & watch options etc...
    return zero
  }

  function useQuery() {
    // wrap the useQuery composable and pass the zero instance
  }

  return {
    useZero,
    useQuery,
  }
}

And then the user can create their own instance composable:

export { useZero, useQuery } = createZero<Schema, Mutators>()

Not sure about the UX though

@maxstevens-nl
Copy link
Collaborator Author

export { useZero, useQuery } = createZero<Schema, Mutators>()

@Gerbuuun This is very interesting, seems like the way to go.

Not sure about the UX though

I think this would actually be better, since this PR already introduces the createUseZero composable combined with a plugin. The plugin can be dropped in this approach.

I'm not sure how well this can be adapted to work in Nuxt, @Gerbuuun are you able to weigh in on that as well?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This createZero function is now a composable. This means it does not work out of the vue context. z is not shared and every time you need to call createZero.

What I meant with my example is to call createZero top-level which then creates the composables useZero and useQuery. That way each time you call the imported composable useZero, you get the already existing instance.

I haven't fully worked it out from there because of reactivity problems. I'll clean up my code and share the branch

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant with my example is to call createZero top-level which then creates the composables useZero and useQuery

These changes achieve exactly that right? The concept is that you call createZero once for each zero instance/session you have in your application. The useZero and useQuery composables can then be exposed to the rest of the application.

This createZero function is now a composable. This means it does not work out of the vue context.

Could you elaborate what doesn't work outside of the vue context?

@Gerbuuun
Copy link
Contributor

I have created a draft PR #137 with my idea and a working playground so you can check it out @maxstevens-nl
I also made some internal changes to Views and added error forwarding but those are not important to understand what I mean.

Please let me know what you think.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants